import { merge } from 'lodash-es'

interface HookOptions {
  chunkSize?: number
  request: PromiseFn
}

export interface ChunkFile {
  id: string
  index: number
  progress: number
  file: File
}

interface Task {
  fn: () => Promise<any>
  retry: number
}

const defaultOptions: Partial<HookOptions> = {
  chunkSize: 1024 * 1024 * 2
}

// 并行任务执行
async function executeParallelTasks(
  funcs: ((...args: any) => any | Promise<any>)[],
  limit: number = 5
) {
  const maxRetryCount = 0
  const start = new Date().getTime()
  const tasks: Task[] = funcs.map(fn => {
    return { fn, retry: 0 }
  })
  const promises: Promise<any>[] = []

  function runTask(task: Task) {
    const p = task.fn()
    if (!(p instanceof Promise)) {
      return
    }
    p.then(() => {
      promises.splice(promises.indexOf(p), 1)
    }).catch(err => {
      if (task.retry === maxRetryCount) {
        throw new Error('任务重试次数耗尽')
      }
      task.retry++
      console.log(`并行任务异常，重试第${task.retry}次`, err)
      promises.splice(promises.indexOf(p), 1)
      runTask(task)
    })
    return p
  }

  // 循环启动任务
  for (const task of tasks) {
    if (promises.length === limit) {
      await Promise.race(promises)
    }
    const p = runTask(task)
    if (!p) {
      return
    }
    promises.push(p)
  }

  await Promise.all(promises)
  console.log(`并行任务耗时: ${new Date().getTime() - start}ms`)
}

export default function useUpload(options: HookOptions) {
  options = merge(options, defaultOptions)

  const chunks = ref<ChunkFile[]>([])
  const fileName = ref('')
  const progress = computed(() => {
    const totalProgress = chunks.value.length
    if (totalProgress === 0) {
      return 0
    }
    const currentProgress = chunks.value.reduce((prev, cur) => {
      return prev + cur.progress
    }, 0)
    return Number(formatFloat((currentProgress / totalProgress) * 100))
  })

  function reset() {
    chunks.value = []
    fileName.value = ''
  }

  // 文件分片
  function splitFile(file: File): ChunkFile[] {
    if (file.size <= options.chunkSize!) {
      return [{ id: uuid(), index: 0, progress: 0, file }]
    }
    const arr: File[] = []
    for (let i = 0; i < file.size; i += options.chunkSize!) {
      const blob = file.slice(i, i + options.chunkSize!)
      const f = new File([blob], 'chunk')
      arr.push(f)
    }
    const chunkId = uuid()
    return arr.map((item, index) => {
      return { id: chunkId, index, progress: 0, file: item }
    })
  }

  // 开始上传任务
  async function start(file: File) {
    fileName.value = file.name
    chunks.value = splitFile(file)

    const fns = chunks.value.map(chunk =>
      options.request.bind(options.request, chunk)
    )
    executeParallelTasks(fns, 1).catch(() => {
      reset()
    })
  }

  return {
    chunks,
    fileName,
    progress,
    start,
    reset
  }
}
